package com.pilhun.parser;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class BeanFactoryImpl implements BeanFactory {

	private static final Map<Class, Class> typeMap;
	private static final Logger logger = Logger.getLogger(BeanFactoryImpl.class.getName());
	private static final String INT_REGEXP = "^[+-]?\\d*$";
	private static final String FLOAT_REGEXP = "(^\\s*[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?[dDfF]?\\s*$)|(^\\s*[-+]?0[xX][0-9a-fA-F]*\\.?[0-9a-fA-F]+([pP][-+]?[0-9a-fA-F]+)?\\s*$)";
	private Map<String, BeanObj> beanMap;
	private Map<String, Object> singletonHolder = new HashMap<String, Object>();
	private Set<String> beanRecursionStack = new HashSet<String>();

	BeanFactoryImpl(Map<String, BeanObj> beanMap) {
		this.beanMap = beanMap;
	}

	static {
        Map<Class, Class> aMap = new HashMap();
        aMap.put(java.lang.Boolean.TYPE, java.lang.Boolean.class);
        aMap.put(java.lang.Byte.TYPE, java.lang.Byte.class);
        aMap.put(java.lang.Character.TYPE, java.lang.Character.class);
        aMap.put(java.lang.Double.TYPE, java.lang.Double.class);
        aMap.put(java.lang.Float.TYPE, java.lang.Float.class);
        aMap.put(java.lang.Integer.TYPE, java.lang.Integer.class);
        aMap.put(java.lang.Long.TYPE, java.lang.Long.class);
        aMap.put(java.lang.Short.TYPE, java.lang.Short.class);
        typeMap = Collections.unmodifiableMap(aMap);
		ConsoleHandler consoleHandler = new ConsoleHandler();
		logger.addHandler(consoleHandler);
	}

	private String getSetterName(String property) {
		return "set" + Character.toUpperCase(property.charAt(0))
				+ property.substring(1);
	}

	private static boolean isAssignablePrimitiveToBoxed(Class primitive,
			Class boxed) {
		if (boxed == primitive){
			return true;
		}
		if (typeMap.containsKey(primitive)){
			return boxed == typeMap.get(primitive);
		}
		return false;
	}

	public Object toObject(Class argType, String argString) {
		if (isAssignablePrimitiveToBoxed(argType, Boolean.class)){
			return Boolean.parseBoolean(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Byte.class)){
			return Byte.parseByte(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Short.class)){
			return Short.parseShort(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Integer.class)){
			return Integer.parseInt(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Long.class)){
			return Long.parseLong(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Float.class)){
			return Float.parseFloat(argString);
		}
		if (isAssignablePrimitiveToBoxed(argType, Double.class)){
			return Double.parseDouble(argString);
		}
		return argString;
	}

	public boolean canBeParsed(Class argType, String argString) {
		if (isAssignablePrimitiveToBoxed(argType, Boolean.class)
				|| String.class == argType) {
			return true;
		}
		if (isAssignablePrimitiveToBoxed(argType, Integer.class)
				|| isAssignablePrimitiveToBoxed(argType, Byte.class)
				|| isAssignablePrimitiveToBoxed(argType, Short.class)
				|| isAssignablePrimitiveToBoxed(argType, Long.class)) {
			Matcher m = Pattern.compile(INT_REGEXP).matcher(argString);
			return m.find();
		}
		if (isAssignablePrimitiveToBoxed(argType, Float.class)
				|| isAssignablePrimitiveToBoxed(argType, Double.class)) {
			Matcher m = Pattern.compile(FLOAT_REGEXP).matcher(argString);
			return m.find();
		}
		return false;
	}

	public <T> Constructor<T> findConstructor(Class<T> beanClass,
			List<ArgumentEntry> argList) throws SecurityException,
			ClassNotFoundException {

		Constructor<?>[] constructors = beanClass.getConstructors();
		outer: for (Constructor<?> constructor : constructors) {
			Class[] argTypes = constructor.getParameterTypes();
			Constructor<T> genericConstructor = (Constructor<T>) constructor;
			if (argTypes.length == argList.size()) {
				for (int i = 0; i < argTypes.length; i++) {
					if (argList.get(i).isVal()
							&& !canBeParsed(argTypes[i], argList.get(i).getArgument())) {
						continue outer;
					}
					if (argList.get(i).isRef()) {
						BeanObj refBean = beanMap.get(argList.get(i).getArgument());
						if (!refBean.getClassName().equals(argTypes[i].getName())){
							continue outer;
						}
					}
				}
				return genericConstructor;
			}
		}
		return null;
	}

	public <T> Method findSetter(Class<T> beanClass, String propertyName)
			throws SecurityException, ClassNotFoundException {
		Method[] methods = beanClass.getMethods();
		for (Method method : methods) {
			String methodName = method.getName();
			if (methodName.equals(getSetterName(propertyName))){
				return method;
			}
		}
		return null;
	}

	public Object createBean(String name) throws SecurityException,
			ClassNotFoundException, InstantiationException,
			IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, CyclicBeanDependencyException,
			NoSuchConstructorException, NoSuchSetterException {
		if (beanRecursionStack.contains(name)){
			throw new CyclicBeanDependencyException();
		}
		beanRecursionStack.add(name);
		BeanObj beanObj = beanMap.get(name);
		String beanClassName = beanObj.getClassName();
		Class beanClass = Class.forName(beanClassName);
		List<ArgumentEntry> argEntries = beanObj.getConstructorArgs();
		Constructor constructor = findConstructor(beanClass, argEntries);
		if (constructor == null){
			throw new NoSuchConstructorException();
		}
		Class[] argTypes = constructor.getParameterTypes();

		Object[] args = new Object[argTypes.length];
		for (int i = 0; i < argTypes.length; i++) {
			ArgumentEntry springArg = argEntries.get(i);
			Class argType = argTypes[i];
			Object arg;
			if (springArg.isVal()) {
				arg = toObject(argType, springArg.getArgument());
			} else {
				arg = getBean(springArg.getArgument());
				if (arg == null) {
					return null;
				}
			}
			args[i] = arg;
		}
		Object bean = constructor.newInstance(args);

		List<ArgumentEntry> propertyEntries = beanObj.getPropertyArgs();
		for (ArgumentEntry property : propertyEntries) {
			Method setter = findSetter(beanClass, property.getName());
			if (setter == null){
				throw new NoSuchSetterException();
			}
			Class propertyType = setter.getParameterTypes()[0];
			Object arg;
			if (property.isVal()) {
				arg = toObject(propertyType, property.getArgument());
			} else {
				arg = getBean(property.getArgument());
				if (arg == null) {
					return null;
				}
			}
			setter.invoke(bean, arg);
		}
		singletonHolder.put(name, bean);
		beanRecursionStack.clear();
		return bean;
	}

	public Object getBean(String name) {
		try {
			if (singletonHolder.containsKey(name)){
				return singletonHolder.get(name);
			} else {
				return createBean(name);
			}
		} catch (SecurityException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (ClassNotFoundException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (InstantiationException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (IllegalAccessException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (IllegalArgumentException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (InvocationTargetException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (CyclicBeanDependencyException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (NoSuchConstructorException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (NoSuchSetterException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		}
		return null;
	}

	public <T extends Object> T getBean(String name, Class<T> type) {
		try {
			if (singletonHolder.containsKey(name)){
				return (T) singletonHolder.get(name);
			} else {
				return (T) createBean(name);
			}
		} catch (SecurityException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (ClassNotFoundException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (InstantiationException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (IllegalAccessException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (IllegalArgumentException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (InvocationTargetException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (CyclicBeanDependencyException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (NoSuchConstructorException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		} catch (NoSuchSetterException e) {
			logger.log(Level.WARNING, "exception was thrown", e);
		}
		return null;
	}
}
